// 搭建五子棋服务器

#ifndef __SERVER__HPP
#define __SERVER__HPP

#include "util.hpp"
#include "Log.hpp"
#include "match.hpp"
#include "room.hpp"
#include "db.hpp"
#include "online_user.hpp"
#include "session.hpp"

#define WEBROOT "./webroot"
/*请求中分为 : http , websocket 通信*/
class GoBangServer
{
    typedef websocketpp::connection_hdl conn;
    typedef wsserver_t::message_ptr message;
    //----- WebSocket 相关帮助函数
private:
    /*websocket 应答*/
    void websocket_resp(const wsserver_t::connection_ptr &conn, Json::Value json_resp)
    {
        std::string body;
        json_util::Serialize(json_resp, body);
        conn->send(body); /*websocket 响应*/
    }

    /*获取 Cookie 信息 , 返回对应的 session 信息*/
    session_ptr ByCookieGetSession(const wsserver_t::connection_ptr &conn, Json::Value &json_resp)
    {
        /*1. 获取请求正文中的 Cookie 信息*/
        std::string cookie_str = conn->get_request_header("Cookie");
        if (cookie_str.empty())
        {
            /*头部信息中就没有 session , 证明没有登录过或登录过期*/
            DBGLOG("获取头部请求失败");
            json_resp["optype"] = "hall_ready";
            json_resp["result"] = false;
            json_resp["reason"] = "获取 Cookie 信息失败 , 请登录 !";
            /*websocket 给客户端应答*/
            websocket_resp(conn, json_resp);
            return session_ptr();
        }
        /*2. 获取 ssid*/
        std::string ssid_str;
        bool ret = GetSessionInfoStr(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            DBGLOG("获取会话信息字符串失败(包括会话ID)");
            json_resp["optype"] = "hall_ready";
            json_resp["result"] = false;
            json_resp["reason"] = "获取 SSID 失败 , 请登录 !";
            /*websocket 给客户端应答*/
            websocket_resp(conn, json_resp);
            return session_ptr();
        }
        /*3. 通过session id 获取 session 会话*/
        session_ptr session = _sm.GetSession(std::stol(ssid_str));
        if (session.get() == nullptr)
        {
            /*这里已经有ssid 证明会话有过 , 但是获取 session 失败 , 证明过期了*/
            DBGLOG("获取会话失败 , 登录过期");
            json_resp["optype"] = "hall_ready";
            json_resp["result"] = false;
            json_resp["reason"] = "登录过期 , 请重新登录 !";
            /*websocket 给客户端应答*/
            websocket_resp(conn, json_resp);
            return session_ptr();
        }

        /*4. 获取成功了 , 返回该 session_ptr */
        return session;
    }

    /*请求的是游戏大厅长连接 , 为匹配做准备*/
    void req_hall(wsserver_t::connection_ptr &conn)
    {
        // 游戏大厅的连接建立成功后
        // 建立成功后要做什么 ?? 玩家要匹配 , 但匹配的前提是要在大厅 , 在大厅的前提就是要登录
        // 所以 :
        Json::Value json_resp;

        // 【1. 判断是否登录】 , 即 : 判断是否能够成功获取 session 会话信息 , 客户端发来的请求中携带 Cookie信息
        session_ptr session = ByCookieGetSession(conn, json_resp);
        if (session.get() == nullptr)
        {
            return;
        }
        DBGLOG("当前用户 : %lu", session->GetUserID());
        // 【2. 判断是否重复登录 , 一个账号只允许一人登录在线】
        if (_oum.IsInGameHall(session->GetUserID()) || _oum.IsInGameRoom(session->GetUserID()))
        {
            DBGLOG("重复登录!");
            json_resp["optype"] = "hall_ready";
            json_resp["result"] = false;
            json_resp["reason"] = "重复登录 !";
            /*websocket 给客户端应答*/
            return websocket_resp(conn, json_resp);
        }
        // 【3. 登录没有问题 , 表示已经成功进入大厅 , 将用户加入大厅】
        _oum.EnterGameHall(session->GetUserID(), conn);
        // 【4. 给客户端返回建立进入大厅成功】
        json_resp["optype"] = "hall_ready";
        json_resp["result"] = true;
        json_resp["reason"] = "建立大厅连接成功";
        websocket_resp(conn, json_resp);
        // 【5. 设置session 为永久存在 , 因为此时已经是长连接了】
        _sm.SetSessionExpireTime(session->GetSessionID(), SESSION_LASTING);
    }

    /*关闭大厅长连接处理 , 因为一旦用户退出对应连接的页面后 , 连接本身会被关闭 , 但是在建立连接处理中将用户加入到大厅了&&session设置为永久存在了*/
    /*如果不进行关闭连接的处理 , 就会导致用户在还大厅 , session 还是永久存在 , 导致资源泄露 !*/
    void req_close_hall(wsserver_t::connection_ptr &conn)
    {
        /*1. 获取连接对应的 session id*/
        /*2. 找到 session id 对应的 session*/
        Json::Value json_resp;
        session_ptr session = ByCookieGetSession(conn, json_resp);
        if (session.get() == nullptr)
        {
            return;
        }
        /*3. 通过 session 会话找到对应的用户 ID*/
        uint64_t uid = session->GetUserID();
        /*4. 通过用户 ID 将该用户从大厅中删除*/
        _oum.ExitGameHall(uid);
        /*5. 将 session 设置过期时间 , 恢复超时销毁状态*/
        _sm.SetSessionExpireTime(session->GetSessionID(), SESSION_TIMEOUT);
    }

    /*游戏大厅消息请求*/
    void req_hall_msg(wsserver_t::connection_ptr &conn, message message)
    {
        // 大厅中 , 只有玩家匹配功能 , 所以请求消息两种 : 1. 开始匹配消息请求 2. 停止匹配请求

        /*1. 获取该连接的 session*/
        Json::Value json_resp;
        session_ptr session = ByCookieGetSession(conn, json_resp);
        if (session.get() == nullptr)
        {
            return;
        }
        /*2. 获取用户 ID*/
        uint64_t uid = session->GetUserID();
        /*3. 获取请求消息*/
        std::string req_body = message->get_payload();
        /*4. 对请求消息反序列化 , 因为请求消息是客户端发来的(是一个序列化的Json串),我们要判断请求类型*/
        Json::Value req_json;
        bool ret = json_util::Unserialize(req_body, req_json);
        if (ret == false)
        {
            // 反序列化失败 , 证明就不知道客户端请求是什么 , 给客户端返回原因
            json_resp["result"] = false;
            json_resp["reason"] = "请求消息失败 !";
            return websocket_resp(conn, json_resp);
        }
        /*5. 判断类型是开始匹配还是停止匹配 , 开始匹配将玩家加入匹配队列 , 反之从队列中移除*/
        if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
        {
            // 开始匹配 -- 将用户添加到匹配队列中并给客户端返回告知信息
            _mthm.AddMatch(uid);
            // 用户添加匹配队列成功
            DBGLOG("玩家 %lu 进入匹配队列成功!", uid);
            json_resp["optype"] = "match_start";
            json_resp["result"] = true;
            return websocket_resp(conn, json_resp);
        }
        else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
        {
            // 停止匹配 -- 将用户从匹配队列中移除并给客户端返回告知信息
            _mthm.DeleteMatch(uid);
            // 用户移除匹配队列成功
            DBGLOG("玩家 %lu 从匹配队列中删除成功!", uid);
            json_resp["optype"] = "match_stop";
            json_resp["result"] = true;
            return websocket_resp(conn, json_resp);
        }

        // 未知类型 ,向客户端返回错误信息
        json_resp["optype"] = "unknow";
        json_resp["result"] = false;
        json_resp["reason"] = "未知的请求类型 !";
        return websocket_resp(conn, json_resp);
    }

    /*请求的是游戏房间中的长连接 , 为玩家在游戏房间中对战/聊天做准备*/
    void req_room(wsserver_t::connection_ptr &conn)
    {
        Json::Value json_resp;
        // 建立游戏房间长连接
        /*1. 获取连接中的 session*/
        session_ptr session = ByCookieGetSession(conn, json_resp);
        if (session.get() == nullptr)
        {
            return;
        }
        /*2. 获取到session之后有了用户信息 , 需要看用户是否重新登录 , 一次只允许一个该用户进入游戏对战*/
        if (_oum.IsInGameHall(session->GetUserID()) || _oum.IsInGameRoom(session->GetUserID()))
        {
            DBGLOG("重复登录!");
            json_resp["optype"] = "room_ready";
            json_resp["result"] = false;
            json_resp["reason"] = "重复登录 !";
            /*websocket 给客户端应答*/
            return websocket_resp(conn, json_resp);
        }
        /*3. 获取该用户的房间 , 看是否有房间就是看是否能通过该用户ID获取到房间信息 , 因为匹配成功后会为该用户创建房间*/
        room_ptr rp = _rm.GetRoomByUserId(session->GetUserID());
        if (rp.get() == nullptr)
        {
            DBGLOG("获取房间信息失败 !");
            json_resp["optype"] = "room_ready";
            json_resp["result"] = false;
            json_resp["reason"] = "获取房间信息失败 !";
            return websocket_resp(conn, json_resp);
        }
        /*4. 将用户加入到在线用户的房间管理中*/
        _oum.EnterGameRoom(session->GetUserID(), conn);
        /*5. 将 session 会话设置为永久存在*/
        _sm.SetSessionExpireTime(session->GetSessionID(), SESSION_LASTING);
        /*6. 建立游戏房间长连接成功 , 给客户端返回游戏房间建立成功的相关信息*/
        DBGLOG("游戏房间长连接建立成功");
        json_resp["optype"] = "room_ready";
        json_resp["result"] = true;
        json_resp["room_id"] = (Json::UInt64)rp->GetRoomID();
        json_resp["self_id"] = (Json::UInt64)session->GetUserID();
        json_resp["white_id"] = (Json::UInt64)rp->GetWhiteID();
        json_resp["black_id"] = (Json::UInt64)rp->GetBlackID();
        return websocket_resp(conn, json_resp);
    }

    /*请求游戏房间长连接的关闭*/
    void req_close_room(wsserver_t::connection_ptr &conn)
    {
        Json::Value json_resp;
        /*1. 获取信息 , 识别客户端*/
        session_ptr session = ByCookieGetSession(conn, json_resp);
        if (session.get() == nullptr)
        {
            return;
        }
        /*2. 将玩家从在线用户管理房间列表中移除*/
        _oum.ExitGameRoom(session->GetUserID());
        /*3. 恢复 session 生命周期 , 设置销毁*/
         _sm.SetSessionExpireTime(session->GetSessionID(), SESSION_TIMEOUT);
        /*4. 将玩家从房间中移除 , 房间中没有玩家就销毁房间*/
        _rm.RemoveRoomUser(session->GetUserID());
    }

    /*游戏房间消息请求*/
    void req_room_msg(wsserver_t::connection_ptr &conn, message message)
    {
    }

    //----- HTTP 相关帮助函数
private:
    /*用户注册帮助函数 , 封装应答信息*/
    void reg_resp_helper(Json::Value &json_resp, wsserver_t::connection_ptr &conn, bool result,
                         const std::string &reason, websocketpp::http::status_code::value code)
    {
        json_resp["result"] = result;
        json_resp["reason"] = reason;
        std::string rsp;
        json_util::Serialize(json_resp, rsp);
        conn->set_status(code);                                  /*设置应答状态码*/
        conn->set_body(rsp);                                     /*设置应答正文内容*/
        conn->append_header("Content-Type", "application/json"); /*设置应答正文格式*/
    }

    /*从请求头部获取 session id 等 ... */
    bool GetSessionInfoStr(const std::string &cookie_str, const std::string &key, std::string &value)
    {
        /*客户端发送请求会携带 Cookie 字段 , 查找 key 对应的 value */
        /*name=value; name=value; 字段是以 : ;+空格 分割*/
        std::string sep = "; "; /*;+空格*/
        std::vector<std::string> key_value;
        string_util::split(cookie_str, sep, key_value); /*name=value ssid=1234 .. */
        /*走到这里所有的 key_value 值都已经被存在 vector 中了*/
        for (auto &str : key_value)
        {
            /*分割每个 key_value 字符串(name=value)*/
            std::vector<std::string> tmp;
            string_util::split(str, "=", tmp); /*name=value*/
            if (tmp.size() != 2)
            {
                continue;
            }
            /*每次只取出 name(下标0) 和 value(下标1) 两个字符串放到 tmp 中*/
            if (tmp[0] == key)
            {
                value = tmp[1];
                return true;
            }
        }
        return false;
    }

private:
    // 处理四种回调函数 && 各种子模块

    /*******************************  HTTP 模块  ******************************* */

    /*【http 的静态资源请求】 : 如 : 请求注册页面 ,获取静态资源的本质就是打开服务器上特定的文件并返回给客户端*/
    void get_static_resources(wsserver_t::connection_ptr &conn)
    {
        /*1. 获取连接请求信息*/
        websocketpp::http::parser::request req = conn->get_request();
        /*2. 获取请求信息的请求方法和请求资源*/
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        // DBGLOG("get uri : %s", uri.c_str());
        /*3. 拼接出请求路径 , 要能够找到该文件*/
        std::string req_path = _webroot + uri; /* ./webroot/login.html .... */
        /*4. 判断路径 , 如果访问根 / 返回注册页面*/
        if (!req_path.empty() && req_path.back() == '/')
        {
            req_path += "/register.html";
        }
        /*5. 打开路径文件获取*/
        std::string resp_body; /*回应内容*/
        bool ret = file_util::read(req_path, resp_body);
        if (ret == true)
        {
            /*打开成功 , 返回内容并设置状态码为 ok*/
            conn->set_body(resp_body);
            conn->set_status(websocketpp::http::status_code::value::ok);
        }
        else
        {
            /*打开失败 , 返回 404 页面并设置状态码为 not found */
            std::string err_path = _webroot + "/404.html";
            std::string err_body;
            bool err_ret = file_util::read(err_path, err_body);
            if (err_ret == true)
            {
                /*404 打开成功 , 返回&&设置状态码not found */
                conn->set_body(err_body);
                conn->set_status(websocketpp::http::status_code::value::not_found);
            }
            else
            {
                /*404 页面没找到 , 返回一个没找到页面文本*/
                std::string no_found = "<html><body><h1>404 访问错误 !</h1></body></html>";
                conn->set_body(no_found);
                conn->set_status(websocketpp::http::status_code::value::not_found);
                conn->append_header("Content-Type", "text/html; charset=utf-8");
            }
        }
        return;
    }

    /*【http 的动态功能 : 注册提交 , 登录提交 , 获取用户信息】*/

    void reg(wsserver_t::connection_ptr &conn)
    {
        Json::Value json_resp;
        /*1. 获取前端发来的连接请求*/
        websocketpp::http::parser::request req = conn->get_request();
        /*2. 获取请求正文*/
        std::string req_body = conn->get_request_body();
        /*3. 处理请求正文 -- 前端发来的请求是 json 串格式 , 对请求正文【反序列化】*/
        Json::Value user_data;
        bool ret = json_util::Unserialize(req_body, user_data);
        if (ret == false)
        {
            DBGLOG("反序列化信息失败 !");
            /*反序列化失败 , 需要返回 , 并告知客户端失败原因*/
            return reg_resp_helper(json_resp, conn, false, "请求正文格式错误 , 请检查 !", websocketpp::http::status_code::value::bad_request);
        }

        /*4. 反序列化成功, 获得了 Json::Value user_data, 检验用户数据 , 要判断传来的字符串是否为空 , 因为数据库中可能有""字符会导致解析为用户名已经存在" */
        if (user_data["username"].isNull() || user_data["username"].asString().empty() || user_data["password"].isNull() || user_data["password"].asString().empty())
        {
            DBGLOG("用户名或密码信息不完整 !");
            /*告知客户端*/
            return reg_resp_helper(json_resp, conn, false, "请输入用户名或密码 !", websocketpp::http::status_code::value::bad_request);
        }

        /*新增*/
        std::string username = user_data["username"].asString();
        if (_ut.ObtainInfoByName(username, user_data))
        {
            // 获取用户信息成功 , 证明已经注册了 , 不在注册
            DBGLOG("已经注册了 , 注册失败 !");
            /*告知客户端*/
            return reg_resp_helper(json_resp, conn, true, "该用户已经注册!", websocketpp::http::status_code::value::ok);
        }

        /*5. 用户数据完整 , 添加到数据库中*/
        bool regs = _ut.Register(user_data);
        if (regs == false)
        {
            DBGLOG("添加用户数据到数据库失败 !");
            /*告知客户端*/
            return reg_resp_helper(json_resp, conn, false, "用户名已经占用 !", websocketpp::http::status_code::value::bad_request);
        }
        else
        {
            /*6. 信息完整 , 已经添加数据库 , 返回注册成功给客户端*/
            DBGLOG("用户注册成功 !");
            return reg_resp_helper(json_resp, conn, true, "注册成功 , 请登录 !", websocketpp::http::status_code::value::ok);
        }
    }

    void login(wsserver_t::connection_ptr &conn)
    {
        Json::Value json_resp;
        /*1. 获取连接的请求正文*/
        std::string req_body = conn->get_request_body();
        /*2. 获取请求中请求内容后 , 对其 【反序列化】(因为客户端传过来的是一个Json串)*/
        Json::Value user_data;
        json_util::Unserialize(req_body, user_data);
        /*3. 对反序列内容中的用户名和密码进行校验*/
        if (user_data["username"].isNull() || user_data["username"].asString().empty() || user_data["password"].isNull() || user_data["password"].asString().empty())
        {
            DBGLOG("用户名和密码不完整");
            return reg_resp_helper(json_resp, conn, false, "请输入用户名或密码 !", websocketpp::http::status_code::value::bad_request);
        }

        /*新增 , 如果直接获取用户信息没有 , 证明用户还未注册*/
        std::string username = user_data["username"].asString();
        if (!_ut.ObtainInfoByName(username, user_data))
        {
            // 获取用户信息成功 , 证明已经注册了 , 不在注册
            DBGLOG("用户未注册 , 无法验证 !");
            /*告知客户端*/
            return reg_resp_helper(json_resp, conn, true, "用户未注册 , 请注册 !", websocketpp::http::status_code::value::ok);
        }

        /*4. 如果用户名和密码完整 , 在数据库中进行校验用户名和密码是否匹配*/
        bool ret = _ut.Login(user_data);
        if (ret == false)
        {
            /*与数据库内容不一致*/
            DBGLOG("内容与数据库内容不一致 , 校验失败 , 登录失败!");
            return reg_resp_helper(json_resp, conn, false, "用户名或密码错误 !", websocketpp::http::status_code::value::bad_request);
        }
        /*5. 如果都匹配 , 为该用户创建一个 session 会话 , 进行会话保持 , 提升用户体验*/
        uint64_t uid = user_data["id"].asUInt64();
        session_ptr ssion = _sm.CreateSession(uid, LOGIN);
        if (ssion.get() == nullptr)
        {
            DBGLOG("创建 session 失败 !");
            return reg_resp_helper(json_resp, conn, false, "创建会话失败 !", websocketpp::http::status_code::value::internal_server_error);
        }
        /*6. 为 session 设置过期时间 , 管理生命周期*/
        _sm.SetSessionExpireTime(ssion->GetSessionID(), SESSION_TIMEOUT);
        /*7. 如果以上都完成 , 设置 Set-Cookie 响应头部并给客户端返回信息 , 登录成功 */
        std::string cookie_id = "SSID=" + std::to_string(ssion->GetSessionID());
        conn->append_header("Set-Cookie", cookie_id);
        DBGLOG("登录成功 !");
        return reg_resp_helper(json_resp, conn, true, "登录成功 !", websocketpp::http::status_code::value::ok);
    }

    /*获取用户信息 , 发送到前端 , 展示 , 默认只有登录成功了才会获取*/
    void Info(wsserver_t::connection_ptr &conn)
    {
        Json::Value json_resp;
        /*1. 获取请求头部的Cookie信息 ,客户端发来的头部字段中有 Cookie: session信息(name=value; name=value;) */
        std::string cookie_str = conn->get_request_header("Cookie"); /*name=value; name=value; */
        if (cookie_str.empty())
        {
            /*头部信息中就没有 session , 证明没有登录过或登录过期*/
            DBGLOG("获取头部请求失败");
            return reg_resp_helper(json_resp, conn, false, "获取 cookie 信息失败 , 请重新登录!", websocketpp::http::status_code::value::bad_request);
        }

        /*Cookie: name=value; name=value;*/
        /*2. 从请求头部字符串中获取 session id , SSID=XXXXX */
        std::string ssid_str;
        bool ret = GetSessionInfoStr(cookie_str, "SSID", ssid_str);
        if (ret == false)
        {
            DBGLOG("获取会话信息字符串失败(包括会话ID)");
            return reg_resp_helper(json_resp, conn, false, "获取会话ID失败 , 请重新登录 !", websocketpp::http::status_code::value::bad_request);
        }
        /*3. 通过session id 获取 session 会话*/
        session_ptr session = _sm.GetSession(std::stol(ssid_str));
        if (session.get() == nullptr)
        {
            /*这里已经有ssid 但是获取 session 失败 , 证明过期了*/
            DBGLOG("获取会话失败 , 登录过期");
            return reg_resp_helper(json_resp, conn, false, "登录过期 , 请重新登录", websocketpp::http::status_code::value::bad_request);
        }
        /*4. 通过 session 会话获取该用户ID*/
        uint64_t uid = session->GetUserID();
        /*5. 通过用户 ID 从数据库获取该用户各种信息(得分 ...)*/
        Json::Value user_info;
        ret = _ut.ObtainInfoByID(uid, user_info);
        if (ret == false)
        {
            DBGLOG("获取用户信息失败");
            return reg_resp_helper(json_resp, conn, false, "找不到用户信息 , 请重新登录 !", websocketpp::http::status_code::value::bad_request);
        }
        /*6. 对获取内容序列化 , 发送至客户端*/
        std::string body;
        json_util::Serialize(user_info, body);
        // DBGLOG("用户信息获取成功 : %s ", body.c_str());
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::value::ok);
        conn->append_header("Content-Type", "application/json");
        /*7. 刷新过期时间 , 延长会话 , 因为获取信息是登录成功才获取的 , 所以延迟会话时间,防止会话在玩家游戏期间过期*/
        _sm.SetSessionExpireTime(session->GetSessionID(), SESSION_TIMEOUT);
    }

    /*http 通信回调 -- 完成静态资源和动态功能的请求 , 并返回给客户端*/
    void http_handler_callback(conn hd1)
    {
        /*1. 获取服务器通信连接 connection*/
        wsserver_t::connection_ptr con = _wsserver.get_con_from_hdl(hd1);
        /*2. 获取连接中的 http 请求对象*/
        websocketpp::http::parser::request req = con->get_request();
        /*3. 通过请求得到请求方法和请求资源*/
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        // DBGLOG("req method : %s", method.c_str());
        // DBGLOG("req uri : %s", uri.c_str());

        /*4. 判断请求方法  && 资源是静态资源(只获取页面)还是动态功能(提交数据等)*/
        if (method == "POST" && uri == "/reg")
        {
            /*请求注册功能*/
            return reg(con);
        }
        else if (method == "POST" && uri == "/login")
        {
            /*请求登录功能*/
            return login(con);
        }
        else if (method == "GET" && uri == "/userinfo")
        {
            /*请求获取用户信息功能*/
            return Info(con);
        }
        else
        {
            /*获取静态资源请求*/
            get_static_resources(con);
        }
    }

    /*******************************  HTTP 模块  ********************************/

    /*******************************  WebSocket 模块  ***************************/

    /*成功建立连接回调*/
    void open_handler_callback(conn hd1)
    {
        // 这里建立的连接是 websocket 连接 , 连接分为两种 :
        // 【一】大厅建立长连接 , 为玩家匹配做准备
        // 【二】游戏房间建立游戏内的长连接

        /*0. 获取 h1 对应的连接*/
        wsserver_t::connection_ptr con = _wsserver.get_con_from_hdl(hd1);
        /*1. 获取连接中的 http 请求对象*/
        websocketpp::http::parser::request req = con->get_request();
        /*2. 获取请求对象中的 , 请求资源*/
        std::string uri = req.get_uri();
        /*2. 判断是什么连接 , 前端请求时会给出请求的资源 , 判断资源即可*/
        if (uri == "/hall")
        {
            /*请求匹配的 websocket 大厅长连接 , 处理该部分的任务*/
            return req_hall(con);
        }
        else if (uri == "/room")
        {
            /*请求房间中的 websocket 房间长连接, 处理该部分的任务*/
            return req_room(con);
        }
    }
    /*成功关闭连接回调*/
    void close_handler_callback(conn hd1)
    {
        // 因为建立的连接是 websocket 连接 , 连接分为两种 :
        // 【一】大厅建立长连接 , 为玩家匹配做准备
        // 【二】游戏房间建立游戏内的长连接
        // 所以 , 关闭连接也要关闭对应的连接
        /*0. 获取 h1 对应的连接*/
        wsserver_t::connection_ptr con = _wsserver.get_con_from_hdl(hd1);
        /*1. 获取连接中的 http 请求对象*/
        websocketpp::http::parser::request req = con->get_request();
        /*2. 获取请求对象中的 , 请求资源*/
        std::string uri = req.get_uri();
        /*2. 判断是什么连接 , 前端请求时会给出请求的资源 , 判断资源即可*/
        if (uri == "/hall")
        {
            /*请求匹配的 websocket 大厅长连接 , 处理该部分的任务*/
            return req_close_hall(con);
        }
        else if (uri == "/room")
        {
            /*请求房间中的 websocket 房间长连接, 处理该部分的任务*/
            return req_close_room(con);
        }
    }
    /*处理消息回调*/
    void message_handler_callback(conn hd1, message message) /*message : wsserver_t::message_ptr */
    {
        // 处理消息情况两种 : 1. 游戏大厅消息处理 2. 游戏房间消息处理
        /*0. 获取 h1 对应的连接*/
        wsserver_t::connection_ptr con = _wsserver.get_con_from_hdl(hd1);
        /*1. 获取连接中的 http 请求对象*/
        websocketpp::http::parser::request req = con->get_request();
        /*2. 获取请求对象中的 , 请求资源*/
        std::string uri = req.get_uri();
        /*2. 判断是什么连接 , 前端请求时会给出请求的资源 , 判断资源即可*/
        if (uri == "/hall")
        {
            /*请求匹配的 websocket 大厅长连接 , 处理该部分的任务*/
            return req_hall_msg(con, message);
        }
        else if (uri == "/room")
        {
            /*请求房间中的 websocket 房间长连接, 处理该部分的任务*/
            return req_room_msg(con, message);
        }
    }

public:
    /*初始化服务器 , 没有初始化的就会走其的默认构造*/
    GoBangServer(const std::string &host, const std::string &username,
                 const std::string &password, const std::string &dbname,
                 unsigned int port = 3306)
        : _ut(host, username, password, dbname, port),
          _rm(&_ut, &_oum),
          _mthm(&_ut, &_rm, &_oum),
          _sm(&_wsserver),
          _webroot(WEBROOT)
    {
        // 初始化服务器
        /*1. 设置日志等级*/
        _wsserver.set_access_channels(websocketpp::log::alevel::none);
        /*2. 初始化 asio 框架*/
        _wsserver.init_asio();
        _wsserver.set_reuse_addr(true);
        /*3. 设置回调函数*/
        _wsserver.set_open_handler(std::bind(&GoBangServer::open_handler_callback, this, std::placeholders::_1));
        _wsserver.set_close_handler(std::bind(&GoBangServer::close_handler_callback, this, std::placeholders::_1));
        _wsserver.set_message_handler(std::bind(&GoBangServer::message_handler_callback, this, std::placeholders::_1, std::placeholders::_2));
        _wsserver.set_http_handler(std::bind(&GoBangServer::http_handler_callback, this, std::placeholders::_1));
    }

    /*启动服务器*/
    void Start(int port)
    {
        /*4. 设置监听接口*/
        _wsserver.listen(port);
        /*5. 获取连接*/
        _wsserver.start_accept();
        /*6. 启动服务器*/
        _wsserver.run();
    }

private:
    /*搭建服务器中用到 websocket , 要实例化服务器对象*/
    wsserver_t _wsserver;
    /*服务器中包含各个模块的管理*/
    user_table _ut;
    OnlineUserManage _oum;
    RoomManage _rm;
    MatchManage _mthm;
    SessionManage _sm;
    /*web 根目录 , 客户端访问页面等访问都是这个目录下的文件*/
    std::string _webroot;
};

#endif
